﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using JetBrains.Annotations;



namespace Fine.Utils.Functions
{

    using bf = BindingFlags;

    public static class ObjectExt
    {

        public static V Map<K,V>(this K key, [NotNull] IDictionary<K,V> map)
        {
            return map[key];
        }


        /// <summary>
        /// Checks whether the <c>value</c> equals to one of the specified <c>options</c>.
        /// This function uses <see cref="EqualityComparer{T}"/> to test on equality.
        /// </summary>
        /// <typeparam name="T">value type.</typeparam>
        /// <param name="value">the value to test.</param>
        /// <param name="options">several options.</param>
        /// <returns>true if the value is equal to one of the options.</returns>
        public static bool In<T>(this T value, [NotNull] params T[] options)
        {
            var equator = EqualityComparer<T>.Default;
            for (int i = 0, n = options.Length; i < n; i++)
                if (equator.Equals(value, options[i]))
                    return true;
            return false;
        }


        /// <summary>
        /// Imports a property of a field.
        /// This method looks for the specified property or filed
        /// and assign the parsed value to it.
        /// </summary>
        /// <param name="obj">object which property or field should be populated.</param>
        /// <param name="name">name of property or field.</param>
        /// <param name="value">string representation of value that should be assigned.</param>
        public static void ImportDatumValue([NotNull] this object obj, 
                                            [NotNull] string name, 
                                            [CanBeNull] string value)
        {
            var objClass = obj.GetType();

            var property = objClass.GetProperty(name);
            if (property != null)
            {
                ImportProperyValue(obj, property, value);
                return;
            }

            var field = objClass.GetField(name);
            if (field != null)
            {
                ImportFieldValue(obj, field, value);
                return;
            }

            throw new NoSuchPropertyOrFieldException(objClass, name);
        }


        private static void ImportProperyValue(object obj, PropertyInfo property, string value)
        {
            var coercedValue = CoerceValue(value, property.PropertyType);
            property.SetValue(obj, coercedValue, null);
        }


        private static void ImportFieldValue(object obj, FieldInfo field, string value)
        {
            var coercedValue = CoerceValue(value, field.FieldType);
            field.SetValue(obj, coercedValue);
        }


        private static object CoerceValue(string value, Type type)
        {
            string tvalue = value.Trim();

            switch (type.Name)
            {
                case "String":
                case "Object":
                    return value;

                case "Boolean":
                    return tvalue.Matching(BooleanTruePattern);

                case "Byte":
                    return Byte.Parse(tvalue);

                case "SByte":
                    return SByte.Parse(tvalue);

                case "Int16":
                    return Int16.Parse(tvalue);

                case "UInt16":
                    return UInt16.Parse(tvalue);

                case "Int32":
                    return Int32.Parse(tvalue);

                case "UInt32":
                    return UInt32.Parse(tvalue);

                case "Int64":
                    return Int64.Parse(tvalue);

                case "UInt64":
                    return UInt64.Parse(tvalue);

                default:
                    var parsingMethod = type.GetMethod("Parse", bf.Public | bf.Static);
                    if (parsingMethod != null)
                    {
                        try
                        {
                            var valueObj = parsingMethod.Invoke(null, new[] { tvalue });

                            // TODO проверить что получился объект нужного типа
                            return valueObj;
                        }
                        catch (Exception e)
                        {
                            throw new UnsupportedTypeException(type, e);
                        }
                    }
                    else
                    {
                        throw new UnsupportedTypeException(type);
                    }
            }
        }


        private static readonly Regex BooleanTruePattern =
                new Regex(@"(t(rue)?|y(es)?|1|\+)");

    }



    /// <summary>
    /// Thrown when the class has no requested property or field.
    /// </summary>
    public class NoSuchPropertyOrFieldException : Exception
    {
        public NoSuchPropertyOrFieldException([NotNull] Type clazz, [NotNull] string name)
            : base("Class {0} has no property or field {1}.".With(clazz.Name, name)) 
        {}
    }



    /// <summary>
    /// Thrown when the given type is not supported.
    /// </summary>
    public class UnsupportedTypeException : Exception
    {
        public UnsupportedTypeException(Type type)
            : this("Type {0} is not supported.".With(type.Name))
        {}


        public UnsupportedTypeException(Type type, Exception innerException)
            : this("Type {0} is not supported.".With(type.Name), innerException)
        {}


        public UnsupportedTypeException(string message)
            : base(message) 
        {}


        public UnsupportedTypeException(string message, Exception innerException)
            : base(message, innerException) 
        {}
    }
}
